-- UTF-8 without BOM
local type = type
local string = string
local char = string.char
local lower = string.lower
local format = string.format
local error = error
local pairs = pairs
local table = table
local sort = table.sort
local concat = table.concat
local ipairs = ipairs
local print = print
local open = io.open
local tostring = tostring
local arg = {...}

local namespace = arg[1] -- for bean namespace
if not namespace then error("ERROR: arg[1] must be namespace") end

local template_hint = "// This file is generated by genbeans tool. DO NOT EDIT! @formatter:off\n"
local template_bean = template_hint .. [=[
package ]=] .. namespace .. [=[;
#<#
import java.lang.reflect.Field;#>#
import #(bean.imports);
#(bean.comment)
@SuppressWarnings({"RedundantIfStatement", "RedundantSuppression", "SwitchStatementWithTooFewBranches", "UnnecessaryLocalVariable"})
public #(bean.final)class #(bean.name) extends Bean<#(bean.name)>
{
	private static final long serialVersionUID = #(bean.uid);
	public  static final int BEAN_TYPE = #(bean.type);
	public  static final String BEAN_TYPENAME = #(bean.name).class.getSimpleName();
	public  static final #(bean.name) BEAN_STUB = new #(bean.name)();
#{#	public  static final #(var.type) #(var.name)#(var.value);#(var.comment2)
#}#
#(#	#(bean.modifier) /*#(var.id3)*/ #(var.final)#(var.type) #(var.name);#(var.comment2)
#)##<#
	public #(bean.name)()
	{
#(##(var.new)#)#	}

	public #(bean.name)(#(##(var.type_i) #(var.name), #)#)
	{
#(#		#(var.init);
#)#	}

#>#	@Override
	public void reset()
	{
#(#		#(var.reset);
#)#	}

	#(bean.param_warning)@Override
	public void assign(#(bean.name) _b_)
	{#<#
		if (_b_ == this) return;
		if (_b_ == null) { reset(); return; }#>#
#(#		#(var.assign);
#)#	}
#(#
	/** @return #(var.comment1) */
	public #(var.type) get#(var.name_u)()
	{
		return #(var.name);
	}
#(var.set)#)#
	@Override
	public int type()
	{
		return BEAN_TYPE;
	}

	@Override
	public String typeName()
	{
		return BEAN_TYPENAME;
	}

	@Override
	public #(bean.name) stub()
	{
		return BEAN_STUB;
	}

	@Override
	public #(bean.name) create()
	{
		return new #(bean.name)();
	}

	@Override
	public int initSize()
	{
		return #(bean.initsize);
	}

	@Override
	public int maxSize()
	{
		return #(bean.maxsize);
	}

	@Override
	public Octets marshal(Octets _s_)
	{
#(##(var.marshal)#)#		return _s_.marshalZero();
	}

	@Override
	public OctetsStream unmarshal(OctetsStream _s_) throws MarshalException
	{
		for (;;) { int _i_ = _s_.unmarshalInt1(), _t_ = _i_ & 3; if ((_i_ >>= 2) == 63) _i_ += _s_.unmarshalInt1(); switch(_i_)
		{
			case 0: return _s_;
#(##(var.unmarshal)#)#			default: _s_.unmarshalSkipVar(_t_);
		}}
	}

	@Override
	public #(bean.name) clone()
	{
		return new #(bean.name)(#(##(var.name), #)#);
	}

	@Override
	public int hashCode()
	{
		int _h_ = (int)serialVersionUID;
#(#		_h_ = _h_ * 16777619 + #(var.hashcode);
#)#		return _h_;
	}

	@Override
	public boolean equals(Object _o_)
	{
		if (_o_ == this) return true;
		if (!(_o_ instanceof #(bean.name))) return false;#<#
		#(bean.name) _b_ = (#(bean.name))_o_;#>#
#(#		if (#(var.equals)) return false;
#)#		return true;
	}

	@Override
	public int compareTo(#(bean.name) _b_)
	{
		if (_b_ == this) return 0;
		if (_b_ == null) return 1;#<#
		int _c_;#>#
#(#		_c_ = #(var.compareto); if (_c_ != 0) return _c_;
#)#		return 0;
	}

	@Override
	public StringBuilder toStringBuilder(StringBuilder _s_)
	{
		_s_.append('{');
#(#		#(var.tostring).append(',');
#)#		return _s_.append('}');
	}
#(bean.attach_java)
	@Override
	public Safe safe(SContext.Safe<?> _parent_)
	{
		return new Safe(this, _parent_);
	}

	@Override
	public Safe safe()
	{
		return new Safe(this, null);
	}

	public static final class Safe extends SContext.Safe<#(bean.name)>
	{
#(##(var.field)#)#
		static
		{
			try
			{
				Class<#(bean.name)> _c_ = #(bean.name).class;
#(##(var.fieldget)#)#			}
			catch (Exception e)
			{
				throw new Error(e);
			}
		}

#(##(var.safecache)#)#
		private Safe(#(bean.name) bean, SContext.Safe<?> _parent_)
		{
			super(bean, _parent_);
		}
#(##(var.getsafe)#(var.setsafe)#)#	}
}
]=]

local template_allbeans = template_hint .. [=[
package ]=] .. namespace .. [=[;
#<#
import jane.core.BeanHandler;
import jane.core.map.IntHashMap;
#>#
/** 全部handlers的获取(自动生成的静态类) */
public final class AllBeans
{
	private AllBeans() {}
#[#
	public static IntHashMap<BeanHandler<?>> get#(hdl.name)Handlers()
	{
		return get#(hdl.name)Handlers(null);
	}

	public static IntHashMap<BeanHandler<?>> get#(hdl.name)Handlers(IntHashMap<BeanHandler<?>> r)
	{
		if (r == null) r = new IntHashMap<>(#(hdl.count) * 2);
#(#		r.put(#(bean.type), new #(hdl.path).#(bean.name)Handler());
#)#		return r;
	}
#]#}
]=]

local template_bean_handler = [=[
package #(hdl.path);

import org.apache.mina.core.session.IoSession;
import jane.core.BeanHandler;
import jane.core.Log;
import jane.core.NetManager;
import ]=] .. namespace .. [=[.#(bean.name);

public final class #(bean.name)Handler implements BeanHandler<#(bean.name)>
{
	@Override
	public #(bean.name) beanStub()
	{
		return #(bean.name).BEAN_STUB;
	}

	/*\
#(#	|*| #(var.type) #(var.name)#(var.value);#(var.comment2)
#)#	\*/

	@Override
	public void onProcess(final NetManager manager, final IoSession session, final #(bean.name) arg)
	{
		Log.debug("{}.onProcess: arg={}", getClass().getName(), arg);
	}
}
]=]

local template_alltables = template_hint .. [=[
package ]=] .. namespace .. [=[;

import #(tables.imports);

/** 全部的数据库表的注册和使用类(自动生成的静态类) */
public final class AllTables
{
	private AllTables() {}
	private static final DBManager _dbm = DBManager.instance();
	/**
	 * 注册全部的数据库表<p>
	 * 用于初始化和注册下面的全部静态成员, 并启动提交线程<br>
	 * 调用前要先初始化数据库管理器: DBManager.instance().startup(...)
	 */
	public static void register() { _dbm.startCommitThread(); }#<#
#>#
#(#	#(table.comment)public static final #(table.table)<#(table.key)#(table.comma)#(table.value), #(table.value).Safe> #(table.name) = _dbm.openTable(#(table.id), "#(table.name)", "#(table.lock)", #(table.cachesize)#(table.comma)#(table.keys), #(table.values));
#)#
	public static final class MetaTable
	{
		private static final ArrayList<MetaTable> metaList = new ArrayList<>(#(tables.count));
		private static final IntHashMap<MetaTable> idMetas = new IntHashMap<>(#(tables.count) * 2);
		private static final HashMap<String, MetaTable> nameMetas = new HashMap<>(#(tables.count) * 2);

		public final TableBase<?> table;
		public final Object keyBeanStub; // Class<?> or Bean<?>
		public final Bean<?> valueBeanStub;

		private MetaTable(TableBase<?> tbl, Object kbs, Bean<?> vbs)
		{
			table = tbl;
			keyBeanStub = kbs;
			valueBeanStub = vbs;
			idMetas.put(tbl.getTableId(), this);
			nameMetas.put(tbl.getTableName(), this);
		}
#<#
		static
		{
#(#			metaList.add(new MetaTable(#(table.name), #(table.keyg), #(table.value).BEAN_STUB));
#)#		}
#>#
		public static MetaTable get(int tableId)
		{
			return idMetas.get(tableId);
		}

		public static MetaTable get(String tableName)
		{
			return nameMetas.get(tableName);
		}

		public static void foreach(Consumer<MetaTable> consumer)
		{
			metaList.forEach(consumer);
		}
	}

	/**
	 * 以下内部类可以单独使用,避免初始化前面的表对象
	 */
	public static final class SimpleMetaTable
	{
		private static final ArrayList<SimpleMetaTable> metaList = new ArrayList<>(#(tables.count));
		private static final IntHashMap<SimpleMetaTable> idMetas = new IntHashMap<>(#(tables.count) * 2);
		private static final HashMap<String, SimpleMetaTable> nameMetas = new HashMap<>(#(tables.count) * 2);

		public final int tableId;
		public final String tableName;
		public final Object keyBeanStub; // Class<?> or Bean<?>
		public final Bean<?> valueBeanStub;

		private SimpleMetaTable(int id, String name, Object kbs, Bean<?> vbs)
		{
			tableId = id;
			tableName = name;
			keyBeanStub = kbs;
			valueBeanStub = vbs;
			idMetas.put(id, this);
			nameMetas.put(name, this);
		}
#<#
		static
		{
#(#			metaList.add(new SimpleMetaTable(#(table.id), "#(table.name)", #(table.keyg), #(table.value).BEAN_STUB));
#)#		}
#>#
		public static SimpleMetaTable get(int tableId)
		{
			return idMetas.get(tableId);
		}

		public static SimpleMetaTable get(String tableName)
		{
			return nameMetas.get(tableName);
		}

		public static void foreach(Consumer<SimpleMetaTable> consumer)
		{
			metaList.forEach(consumer);
		}
	}
}
]=]

local function spairs(t)
	local s = {}
	for k in pairs(t) do
		s[#s + 1] = k
	end
	sort(s)
	local i = 1
	return function()
		local k = s[i]
		i = i + 1
		return k, k and t[k]
	end
end

local typedef = {}
local function merge(ta, tb)
	local r = {}
	for k, v in pairs(ta) do r[k] = v end
	for k, v in pairs(tb) do r[k] = v end
	return r
end
local function typename(var, t)
	local def = typedef[t]
	if not def then return t end
	if type(def) == "function" then
		local t = { id = 0, import = {} } def(t, 0) def = t
		for k in pairs(t.import) do var.import[k] = true end
	end
	if type(def) == "table" then return type(def.type) == "string" and def.type or def.type(var) end
	error("ERROR: unknown typename(" .. var .. ", " .. t .. ")")
end
local function subtypename(var, t)
	local def = typedef[t]
	if not def then return t end
	if type(def) == "function" then
		local t = { id = 0, import = {} } def(t, 0) def = t
		for k in pairs(t.import) do var.import[k] = true end
	end
	if type(def) == "table" then
		if def.type_o == "Octets" then var.import[#var.import + 1] = "jane.core.Octets" end
		return type(def.type_o) == "string" and def.type_o or def.type_o(var)
	end
	error("ERROR: unknown subtypename(" .. var .. ", " .. t .. ")")
end
local function subtypename_safe(var, t)
	return typedef[t] and subtypename(var, t) or (t .. ".Safe")
end
local function subtypeid(t)
	local def = typedef[t]
	if not def then def = typedef.bean end
	if type(def) == "function" then local t = { id = 0, import = {} } def(t, 0) return t.subtypeid end
	if type(def) == "table" then return def.subtypeid end
	error("ERROR: unknown subtypeid(" .. t .. ")")
end
local function get_unmarshal_kv(var, kv, t)
	local s = (typedef[var[kv]] or typedef.bean).unmarshal_kv
	return type(s) == "string" and s or s(var, kv, t)
end
typedef.byte =
{
	import = { "jane.core.SBase" },
	name_u = function(var)
		local name = var.name
		local c1, c2 = name:byte(1, 2)
		if c1 >= 0x61 and c1 <= 0x7a and (not c2 or c2 < 0x41 or c2 > 0x5a) then -- [a-z][^A-Z]
			return char(c1 - 0x20) .. name:sub(2)
		end
		return name
	end,
	type = "byte", type_i = "byte", type_o = "Byte",
	subtypeid = 0,
	final = function(var) return var.id > 0 and "" or "transient " end,
	new = "",
	field = "\t\tprivate static final Field FIELD_#(var.name);\n",
	fieldget = "\t\t\t\tFIELD_#(var.name) = _c_.getDeclaredField(\"#(var.name)\"); FIELD_#(var.name).setAccessible(true);\n",
	safecache = "",
	init = "this.#(var.name) = #(var.name)",
	reset = "#(var.name) = 0",
	assign = "this.#(var.name) = _b_.#(var.name)",
	set = [[

	/** @param #(var.name) #(var.comment1) */
	public void set#(var.name_u)(#(var.type) #(var.name))
	{
		this.#(var.name) = #(var.name);
	}
]],
	getsafe = [[

		/** @return #(var.comment1) */
		public #(var.type) get#(var.name_u)()
		{
			return _bean.get#(var.name_u)();
		}
]],
	setsafe = [[

		/** @param #(var.name) #(var.comment1) */
		public void set#(var.name_u)(#(var.type) #(var.name))
		{
			if (initSContext()) _sctx.addOnRollback(new SBase.S#(var.type_o)(_bean, FIELD_#(var.name), _bean.get#(var.name_u)()));
			_bean.set#(var.name_u)(#(var.name));
		}
]],
	marshal = function(var)
		if var.id <= 0 then return "" end
		return var.id < 63 and
			format("\t\tif(this.#(var.name) != 0) _s_.marshal1((byte)0x%02x).marshal(this.#(var.name));\n", var.id * 4) or
			format("\t\tif(this.#(var.name) != 0) _s_.marshal2(0x%04x).marshal(this.#(var.name));\n", 0xfc00 + var.id - 63)
	end,
	unmarshal = function(var)
		return var.id > 0 and "\t\t\tcase #(var.id): this.#(var.name) = (#(var.type))_s_.unmarshalInt(_t_); break;\n" or ""
	end,
	unmarshal_kv = function(var, kv, t) if kv then return "(" .. typename(var, var[kv]) .. ")_s_.unmarshalIntKV(" .. t .. ")" end end,
	hashcode = "this.#(var.name)",
	equals = "this.#(var.name) != _b_.#(var.name)",
	compareto = "this.#(var.name) - _b_.#(var.name)",
	tostring = "_s_.append(this.#(var.name))",
}
typedef.char  = merge(typedef.byte, { type = "char",  type_i = "char",  type_o = "Char"  })
typedef.short = merge(typedef.byte, { type = "short", type_i = "short", type_o = "Short" })
typedef.int = merge(typedef.byte,
{
	type = "int",
	type_i = "int",
	type_o = "Integer",
	unmarshal = function(var)
		return var.id > 0 and "\t\t\tcase #(var.id): this.#(var.name) = _s_.unmarshalInt(_t_); break;\n" or ""
	end,
	unmarshal_kv = function(var, kv, t) if kv then return "_s_.unmarshalIntKV(" .. t .. ")" end end,
	compareto = "Integer.compare(this.#(var.name), _b_.#(var.name))",
})
typedef.long = merge(typedef.byte,
{
	type = "long",
	type_i = "long",
	type_o = "Long",
	unmarshal = function(var)
		return var.id > 0 and "\t\t\tcase #(var.id): this.#(var.name) = _s_.unmarshalLong(_t_); break;\n" or ""
	end,
	unmarshal_kv = function(var, kv, t) if kv then return "_s_.unmarshalLongKV(" .. t .. ")" end end,
	hashcode = "(int)this.#(var.name)",
	compareto = "Long.compare(this.#(var.name), _b_.#(var.name))",
})
typedef.bool = merge(typedef.byte,
{
	type = "boolean", type_i = "boolean", type_o = "Boolean",
	reset = "#(var.name) = false",
	marshal = function(var)
		if var.id <= 0 then return "" end
		return var.id < 63 and
			format("\t\tif(this.#(var.name)) _s_.marshal2(0x%04x);\n", var.id * 0x400 + 1) or
			format("\t\tif(this.#(var.name)) _s_.marshal3(0x%06x);\n", 0xfc0001 + (var.id - 63) * 0x100)
	end,
	unmarshal = function(var)
		return var.id > 0 and "\t\t\tcase #(var.id): this.#(var.name) = (_s_.unmarshalLong(_t_) != 0); break;\n" or ""
	end,
	unmarshal_kv = function(var, kv, t) if kv then return "(_s_.unmarshalLongKV(" .. t .. ") != 0)" end end,
	hashcode = "(this.#(var.name) ? 0xcafebabe : 0xdeadbeef)",
	compareto = "Boolean.compare(this.#(var.name), _b_.#(var.name))",
})
typedef.float = merge(typedef.byte,
{
	type = "float", type_i = "float", type_o = "Float",
	subtypeid = 4,
	marshal = function(var)
		if var.id <= 0 then return "" end
		return var.id < 63 and
			format("\t\tif(this.#(var.name) != 0) _s_.marshal2(0x%04x).marshal(this.#(var.name));\n", var.id * 0x400 + 0x308) or
			format("\t\tif(this.#(var.name) != 0) _s_.marshal3(0x%06x).marshal(this.#(var.name));\n", 0xff0008 + (var.id - 63) * 0x100)
	end,
	unmarshal = function(var)
		return var.id > 0 and "\t\t\tcase #(var.id): this.#(var.name) = _s_.unmarshalFloat(_t_); break;\n" or ""
	end,
	unmarshal_kv = function(var, kv, t) if kv then return "_s_.unmarshalFloatKV(" .. t .. ")" end end,
	hashcode = "Float.floatToRawIntBits(this.#(var.name))",
	compareto = "Float.compare(this.#(var.name), _b_.#(var.name))",
})
typedef.double = merge(typedef.byte,
{
	type = "double", type_i = "double", type_o = "Double",
	subtypeid = 5,
	marshal = function(var)
		if var.id <= 0 then return "" end
		return var.id < 63 and
			format("\t\tif(this.#(var.name) != 0) _s_.marshal2(0x%04x).marshal(this.#(var.name));\n", var.id * 0x400 + 0x309) or
			format("\t\tif(this.#(var.name) != 0) _s_.marshal3(0x%06x).marshal(this.#(var.name));\n", 0xff0009 + (var.id - 63) * 0x100)
	end,
	unmarshal = function(var)
		return var.id > 0 and "\t\t\tcase #(var.id): this.#(var.name) = _s_.unmarshalDouble(_t_); break;\n" or ""
	end,
	unmarshal_kv = function(var, kv, t) if kv then return "_s_.unmarshalDoubleKV(" .. t .. ")" end end,
	hashcode = "(int)((Double.doubleToRawLongBits(this.#(var.name)) * 0x100000001L) >> 32)",
	compareto = "Double.compare(this.#(var.name), _b_.#(var.name))",
})
typedef.string = merge(typedef.byte,
{
	import = { "jane.core.SBase" },
	type = "String", type_i = "String", type_o = "String",
	subtypeid = 1,
	new = "\t\t#(var.name) = \"\";\n",
	init = "this.#(var.name) = (#(var.name) != null ? #(var.name) : \"\")",
	reset = "#(var.name) = \"\"",
	assign = "this.#(var.name) = (_b_.#(var.name) != null ? _b_.#(var.name) : \"\")",
	set = [[

	/** @param #(var.name) #(var.comment1) */
	public void set#(var.name_u)(#(var.type) #(var.name))
	{
		this.#(var.name) = (#(var.name) != null ? #(var.name) : "");
	}
]],
	setsafe = [[

		/** @param #(var.name) #(var.comment1) */
		public void set#(var.name_u)(#(var.type) #(var.name))
		{
			if (initSContext()) _sctx.addOnRollback(new SBase.SObject(_bean, FIELD_#(var.name), _bean.get#(var.name_u)()));
			_bean.set#(var.name_u)((#(var.name) != null ? #(var.name) : ""));
		}
]],
	marshal = function(var)
		if var.id <= 0 then return "" end
		return var.id < 63 and
			format("\t\tif(!this.#(var.name).isEmpty()) _s_.marshal1((byte)0x%02x).marshal(this.#(var.name));\n", var.id * 4 + 1) or
			format("\t\tif(!this.#(var.name).isEmpty()) _s_.marshal2(0x%04x).marshal(this.#(var.name));\n", 0xfd00 + var.id - 63)
	end,
	unmarshal = function(var)
		return var.id > 0 and "\t\t\tcase #(var.id): this.#(var.name) = _s_.unmarshalString(_t_); break;\n" or ""
	end,
	unmarshal_kv = function(var, kv, t) if kv then return "_s_.unmarshalStringKV(" .. t .. ")" end end,
	hashcode = "this.#(var.name).hashCode()",
	equals = "!this.#(var.name).equals(_b_.#(var.name))",
	compareto = "this.#(var.name).compareTo(_b_.#(var.name))",
})
typedef.octets = merge(typedef.string,
{
	import = { "jane.core.Octets", "jane.core.DynBean", "jane.core.SBase" },
	type = "Octets", type_i = "Octets", type_o = "Octets",
	new = "\t\t#(var.name) = new Octets(#(var.cap));\n",
	init = "this.#(var.name) = (#(var.name) != null ? #(var.name) : new Octets(#(var.cap)))",
	reset = "#(var.name).clear()",
	assign = "if (_b_.#(var.name) != null) this.#(var.name).replace(_b_.#(var.name)); else this.#(var.name).clear()",
	set = [[

	/** @param #(var.name) #(var.comment1) */
	public void set#(var.name_u)(#(var.type) #(var.name))
	{
		this.#(var.name) = (#(var.name) != null ? #(var.name) : new Octets(#(var.cap)));
	}

	/** #(var.comment1) */
	public void marshal#(var.name_u)(Bean<?> _b_)
	{
		this.#(var.name).resize(0);
		this.#(var.name).reserve(_b_.initSize());
		_b_.marshal(this.#(var.name));
	}

	/** #(var.comment1) */
	public <B extends Bean<B>> B unmarshal#(var.name_u)(B _b_) throws MarshalException
	{
		_b_.unmarshal(OctetsStream.wrap(this.#(var.name)));
		return _b_;
	}

	/** #(var.comment1) */
	public DynBean unmarshal#(var.name_u)() throws MarshalException
	{
		DynBean _b_ = new DynBean();
		_b_.unmarshal(OctetsStream.wrap(this.#(var.name)));
		return _b_;
	}
]],
	getsafe = [[

		/** @return #(var.comment1) */
		public #(var.type) get#(var.name_u)()
		{
			return _bean.get#(var.name_u)().clone();
		}

		/** @param #(var.name) #(var.comment1) */
		public void set#(var.name_u)(#(var.type) #(var.name))
		{
			if (initSContext()) _sctx.addOnRollback(new SBase.SOctets(_bean, FIELD_#(var.name), _bean.get#(var.name_u)(), false));
			_bean.set#(var.name_u)((#(var.name) != null ? #(var.name).clone() : new Octets(#(var.cap))));
		}

		/** #(var.comment1) */
		public byte[] copyOf#(var.name_u)()
		{
			return _bean.get#(var.name_u)().getBytes();
		}

		/** #(var.comment1) */
		public void marshal#(var.name_u)(Bean<?> _b_)
		{
			if (initSContext()) _sctx.addOnRollback(new SBase.SOctets(_bean, FIELD_#(var.name), _bean.get#(var.name_u)(), false));
			_bean.set#(var.name_u)(_b_.marshal(new Octets(_b_.initSize())));
		}

		/** #(var.comment1) */
		public <B extends Bean<B>> B unmarshal#(var.name_u)(B _b_) throws MarshalException
		{
			return _bean.unmarshal#(var.name_u)(_b_);
		}

		/** #(var.comment1) */
		public DynBean unmarshal#(var.name_u)() throws MarshalException
		{
			return _bean.unmarshal#(var.name_u)();
		}

		/** @return #(var.comment1) */
		@Deprecated
		public #(var.type) unsafe#(var.name_u)()
		{
			return _bean.get#(var.name_u)();
		}
]],
	setsafe = "",
	marshal = function(var)
		if var.id <= 0 then return "" end
		return var.id < 63 and
			format("\t\tif(!this.#(var.name).empty()) _s_.marshal1((byte)0x%02x).marshal(this.#(var.name));\n", var.id * 4 + 1) or
			format("\t\tif(!this.#(var.name).empty()) _s_.marshal2(0x%04x).marshal(this.#(var.name));\n", 0xfd00 + var.id - 63)
	end,
	unmarshal = function(var)
		return var.id > 0 and "\t\t\tcase #(var.id): _s_.unmarshal(this.#(var.name), _t_); break;\n" or ""
	end,
	unmarshal_kv = function(var, kv, t) if kv then return "_s_.unmarshalOctetsKV(" .. t .. ")" end end,
	tostring = "this.#(var.name).toStringBuilder(_s_)",
})
typedef.vector = merge(typedef.octets,
{
	import = { "java.util.ArrayList", "java.util.Collection", "jane.core.Util", "jane.core.SList" },
	type = function(var) return "ArrayList<" .. subtypename(var, var.k) .. ">" end,
	type_i = function(var) return "Collection<" .. subtypename(var, var.k) .. ">" end,
	stype = function(var) return "SList<" .. subtypename(var, var.k) .. ", " .. subtypename_safe(var, var.k) .. ">" end,
	final = function(var) return var.id > 0 and "final " or "transient " end,
	field = "",
	fieldget = "",
	new = function(var) return "\t\t#(var.name) = new ArrayList<>(#(var.cap));\n" end,
	init = function(var) return "Util.appendDeep(#(var.name), this.#(var.name) = new ArrayList<>(#(var.cap)))" end,
	assign = "this.#(var.name).clear(); Util.appendDeep(_b_.#(var.name), this.#(var.name))",
	set = "",
	getsafe = [[

		/** @return #(var.comment1) */
		public #(var.stype) get#(var.name_u)()
		{
			return new #(var.stype)(this, _bean.get#(var.name_u)());
		}

		/** @return #(var.comment1) */
		@Deprecated
		public #(var.type) unsafe#(var.name_u)()
		{
			return _bean.get#(var.name_u)();
		}
]],
	marshal = function(var)
		if var.id <= 0 then return "" end
		return var.id < 63 and
			format([[		if (!this.#(var.name).isEmpty())
		{
			int _i_ = 0, _n_ = this.#(var.name).size();
			_s_.marshal2(0x%04x).marshalUInt(_n_);
			do
				_s_.marshal(this.#(var.name).get(_i_));
			while (++_i_ < _n_);
		}
]], var.id * 0x400 + 0x300 + subtypeid(var.k)) or
			format([[		if (!this.#(var.name).isEmpty())
		{
			int _i_ = 0, _n_ = this.#(var.name).size();
			_s_.marshal3(0x%06x).marshalUInt(_n_);
			do
				_s_.marshal(this.#(var.name).get(_i_));
			while (++_i_ < _n_);
		}
]], 0xff0000 + (var.id - 63) * 0x100 + subtypeid(var.k))
	end,
	unmarshal = function(var)
		return var.id <= 0 and "" or format([[
			case #(var.id):
			{
				this.#(var.name).clear();
				if (_t_ != 3) { _s_.unmarshalSkipVar(_t_); break; }
				_t_ = _s_.unmarshalInt1();
				if (_t_ >= 8) { _s_.unmarshalSkipVarSub(_t_); break; }
				int _n_ = _s_.unmarshalUInt();
				this.#(var.name).ensureCapacity(_n_ < 1000 ? _n_ : 1000);
				for (; _n_ > 0; --_n_)
					this.#(var.name).add(%s);
			} break;
]], get_unmarshal_kv(var, "k", "_t_"))
	end,
	compareto = "Util.compareTo(this.#(var.name), _b_.#(var.name))",
	tostring = "Util.append(_s_, this.#(var.name))",
})
typedef.list = merge(typedef.vector,
{
	import = { "java.util.LinkedList", "java.util.Collection", "jane.core.Util", "jane.core.SList" },
	type = function(var) return "LinkedList<" .. subtypename(var, var.k) .. ">" end,
	new = function(var) return "\t\t#(var.name) = new LinkedList<>();\n" end,
	init = function(var) return "Util.appendDeep(#(var.name), this.#(var.name) = new LinkedList<>())" end,
	marshal = function(var)
		if var.id <= 0 then return "" end
		return var.id < 63 and
			format([[		if (!this.#(var.name).isEmpty())
		{
			_s_.marshal2(0x%04x).marshalUInt(this.#(var.name).size());
			for (%s v : this.#(var.name))
				_s_.marshal(v);
		}
]], var.id * 0x400 + 0x300 + subtypeid(var.k), subtypename(var, var.k)) or
			format([[		if (!this.#(var.name).isEmpty())
		{
			_s_.marshal3(0x%06x).marshalUInt(this.#(var.name).size());
			for (%s v : this.#(var.name))
				_s_.marshal(v);
		}
]], 0xff0000 + (var.id - 63) * 0x100 + subtypeid(var.k), subtypename(var, var.k))
	end,
	unmarshal = function(var)
		return var.id <= 0 and "" or format([[
			case #(var.id):
			{
				this.#(var.name).clear();
				if (_t_ != 3) { _s_.unmarshalSkipVar(_t_); break; }
				_t_ = _s_.unmarshalInt1();
				if (_t_ >= 8) { _s_.unmarshalSkipVarSub(_t_); break; }
				for (int _n_ = _s_.unmarshalUInt(); _n_ > 0; --_n_)
					this.#(var.name).add(%s);
			} break;
]], get_unmarshal_kv(var, "k", "_t_"))
	end,
})
typedef.deque = merge(typedef.list,
{
	import = { "java.util.ArrayDeque", "java.util.Collection", "jane.core.Util", "jane.core.SDeque" },
	type = function(var) return "ArrayDeque<" .. subtypename(var, var.k) .. ">" end,
	stype = function(var) return "SDeque<" .. subtypename(var, var.k) .. ", " .. subtypename_safe(var, var.k) .. ">" end,
	new = function(var) return "\t\t#(var.name) = new ArrayDeque<>(#(var.cap));\n" end,
	init = function(var) return "Util.appendDeep(#(var.name), this.#(var.name) = new ArrayDeque<>(#(var.cap)))" end,
})
typedef.hashset = merge(typedef.list,
{
	import = { "java.util.HashSet", "java.util.Collection", "jane.core.Util", "jane.core.SSet", "jane.core.SSet.SSetListener" },
	type = function(var) return "HashSet<" .. subtypename(var, var.k) .. ">" end,
	stype = function(var) return "SSet<" .. subtypename(var, var.k) .. ", " .. subtypename_safe(var, var.k) .. ">" end,
	field = function(var) return [[
		private static SSetListener<]] .. subtypename(var, var.k) .. [[> LISTENER_#(var.name);
]] end,
	safecache = "\t\tprivate #(var.stype) CACHE_#(var.name);\n",
	new = function(var) return "\t\t#(var.name) = new HashSet<>(#(var.cap));\n" end,
	init = function(var) return "Util.appendDeep(#(var.name), this.#(var.name) = new HashSet<>(#(var.cap)))" end,
	getsafe = function(var) return [[

		/** #(var.comment1) */
		public static void onListen#(var.name_u)(SSetListener<]] .. subtypename(var, var.k) .. [[> _listener_)
		{
			LISTENER_#(var.name) = _listener_;
		}

		/** @return #(var.comment1) */
		public #(var.stype) get#(var.name_u)()
		{
			if (CACHE_#(var.name) == null) CACHE_#(var.name) = new #(var.stype)(this, _bean.get#(var.name_u)(), LISTENER_#(var.name));
			return CACHE_#(var.name);
		}

		/** @return #(var.comment1) */
		@Deprecated
		public #(var.type) unsafe#(var.name_u)()
		{
			return _bean.get#(var.name_u)();
		}
]] end,
})
typedef.treeset = merge(typedef.hashset,
{
	import = { "java.util.TreeSet", "java.util.Collection", "jane.core.Util", "jane.core.SSSet", "jane.core.SSet.SSetListener" },
	type = function(var) return "TreeSet<" .. subtypename(var, var.k) .. ">" end,
	stype = function(var) return "SSSet<" .. subtypename(var, var.k) .. ", " .. subtypename_safe(var, var.k) .. ">" end,
	new = function(var) return "\t\t#(var.name) = new TreeSet<>();\n" end,
	init = function(var) return "Util.appendDeep(#(var.name), this.#(var.name) = new TreeSet<>())" end,
})
typedef.linkedhashset = merge(typedef.hashset,
{
	import = { "java.util.LinkedHashSet", "java.util.Collection", "jane.core.Util", "jane.core.SSet", "jane.core.SSet.SSetListener" },
	type = function(var) return "LinkedHashSet<" .. subtypename(var, var.k) .. ">" end,
	new = function(var) return "\t\t#(var.name) = new LinkedHashSet<>(#(var.cap));\n" end,
	init = function(var) return "Util.appendDeep(#(var.name), this.#(var.name) = new LinkedHashSet<>(#(var.cap)))" end,
})
typedef.hashmap = merge(typedef.list,
{
	import = { "java.util.HashMap", "java.util.Map.Entry", "java.util.Map", "jane.core.Util", "jane.core.SMap", "jane.core.SMap.SMapListener" },
	type = function(var) return "HashMap<" .. subtypename(var, var.k) .. ", " .. subtypename(var, var.v) .. ">" end,
	type_i = function(var) return "Map<" .. subtypename(var, var.k) .. ", " .. subtypename(var, var.v) .. ">" end,
	stype = function(var) return "SMap<" .. subtypename(var, var.k) .. ", " .. subtypename(var, var.v) .. ", " .. subtypename_safe(var, var.v) .. ">" end,
	field = function(var) return [[
		private static SMapListener<]] .. subtypename(var, var.k) .. ", " .. subtypename(var, var.v) .. [[> LISTENER_#(var.name);
]] end,
	safecache = "\t\tprivate #(var.stype) CACHE_#(var.name);\n",
	new = function(var) return "\t\t#(var.name) = new HashMap<>(#(var.cap));\n" end,
	init = function(var) return "Util.appendDeep(#(var.name), this.#(var.name) = new HashMap<>(#(var.cap)))" end,
	getsafe = function(var) return [[

		/** #(var.comment1) */
		public static void onListen#(var.name_u)(SMapListener<]] .. subtypename(var, var.k) .. ", " .. subtypename(var, var.v) .. [[> _listener_)
		{
			LISTENER_#(var.name) = _listener_;
		}

		/** @return #(var.comment1) */
		public #(var.stype) get#(var.name_u)()
		{
			if (CACHE_#(var.name) == null) CACHE_#(var.name) = new #(var.stype)(this, _bean.get#(var.name_u)(), LISTENER_#(var.name));
			return CACHE_#(var.name);
		}

		/** @return #(var.comment1) */
		@Deprecated
		public #(var.type) unsafe#(var.name_u)()
		{
			return _bean.get#(var.name_u)();
		}
]] end,
	marshal = function(var)
		if var.id <= 0 then return "" end
		return var.id < 63 and
			format([[		if (!this.#(var.name).isEmpty())
		{
			_s_.marshal2(0x%04x).marshalUInt(this.#(var.name).size());
			for (Entry<%s, %s> e : this.#(var.name).entrySet())
				_s_.marshal(e.getKey()).marshal(e.getValue());
		}
]], var.id * 0x400 + 0x340 + subtypeid(var.k) * 8 + subtypeid(var.v), subtypename(var, var.k), subtypename(var, var.v)) or
			format([[		if (!this.#(var.name).isEmpty())
		{
			_s_.marshal3(0x%06x).marshalUInt(this.#(var.name).size());
			for (Entry<%s, %s> e : this.#(var.name).entrySet())
				_s_.marshal(e.getKey()).marshal(e.getValue());
		}
]], 0xff0040 + (var.id - 63) * 0x100 + subtypeid(var.k) * 8 + subtypeid(var.v), subtypename(var, var.k), subtypename(var, var.v))
	end,
	unmarshal = function(var)
		return var.id <= 0 and "" or format([[
			case #(var.id):
			{
				this.#(var.name).clear();
				if (_t_ != 3) { _s_.unmarshalSkipVar(_t_); break; }
				_t_ = _s_.unmarshalInt1();
				if ((_t_ >> 6) != 1) { _s_.unmarshalSkipVarSub(_t_); break; }
				int _k_ = (_t_ >> 3) & 7; _t_ &= 7;
				for (int _n_ = _s_.unmarshalUInt(); _n_ > 0; --_n_)
					this.#(var.name).put(%s, %s);
			} break;
]], get_unmarshal_kv(var, "k", "_k_"), get_unmarshal_kv(var, "v", "_t_"))
	end,
})
typedef.treemap = merge(typedef.hashmap,
{
	import = { "java.util.TreeMap", "java.util.Map.Entry", "java.util.Map", "jane.core.Util", "jane.core.SSMap", "jane.core.SMap.SMapListener" },
	type = function(var) return "TreeMap<" .. subtypename(var, var.k) .. ", " .. subtypename(var, var.v) .. ">" end,
	stype = function(var) return "SSMap<" .. subtypename(var, var.k) .. ", " .. subtypename(var, var.v) .. ", " .. subtypename_safe(var, var.v) .. ">" end,
	new = function(var) return "\t\t#(var.name) = new TreeMap<>();\n" end,
	init = function(var) return "Util.appendDeep(#(var.name), this.#(var.name) = new TreeMap<>())" end,
})
typedef.linkedhashmap = merge(typedef.hashmap,
{
	import = { "java.util.LinkedHashMap", "java.util.Map.Entry", "java.util.Map", "jane.core.Util", "jane.core.SMap", "jane.core.SMap.SMapListener" },
	type = function(var) return "LinkedHashMap<" .. subtypename(var, var.k) .. ", " .. subtypename(var, var.v) .. ">" end,
	new = function(var) return "\t\t#(var.name) = new LinkedHashMap<>(#(var.cap));\n" end,
	init = function(var) return "Util.appendDeep(#(var.name), this.#(var.name) = new LinkedHashMap<>(#(var.cap)))" end,
})
typedef.bean = merge(typedef.octets,
{
	import = {},
	type = function(var) return var.type end,
	type_i = function(var) return var.type end,
	type_o = function(var) return var.type end,
	subtypeid = 2,
	final = function(var) return var.id > 0 and "final " or "transient " end,
	field = "",
	fieldget = "",
	new = function(var) return "\t\t#(var.name) = new " .. var.type .. "();\n" end,
	init = function(var) return "this.#(var.name) = (#(var.name) != null ? #(var.name).clone() : new " .. var.type .. "())" end,
	reset = "#(var.name).reset()",
	assign = "this.#(var.name).assign(_b_.#(var.name))",
	set = "",
	getsafe = [[

		/** @return #(var.comment1) */
		public #(var.type).Safe get#(var.name_u)()
		{
			return _bean.get#(var.name_u)().safe(this);
		}

		/** @return #(var.comment1) */
		@Deprecated
		public #(var.type) unsafe#(var.name_u)()
		{
			return _bean.get#(var.name_u)();
		}
]],
	setsafe = "",
	marshal = function(var)
		if var.id <= 0 then return "" end
		return var.id < 63 and
			format([[
		{
			int _n_ = _s_.size();
			this.#(var.name).marshal(_s_.marshal1((byte)0x%02x));
			if (_s_.size() - _n_ < 3) _s_.resize(_n_);
		}
]], var.id * 4 + 2) or
			format([[
		{
			int _n_ = _s_.size();
			this.#(var.name).marshal(_s_.marshal2(0x%04x));
			if (_s_.size() - _n_ < 3) _s_.resize(_n_);
		}
]], 0xfe00 + var.id - 63)
	end,
	unmarshal = function(var)
		return var.id > 0 and "\t\t\tcase #(var.id): _s_.unmarshalBean(this.#(var.name), _t_); break;\n" or ""
	end,
	unmarshal_kv = function(var, kv, t) if kv then return "_s_.unmarshalBeanKV(new " .. typename(var, var[kv]) .. "(), " .. t .. ")" end end,
	compareto = "this.#(var.name).compareTo(_b_.#(var.name))",
})
typedef.ref = merge(typedef.bean,
{
	final = function(var) return var.id > 0 and "" or "transient " end,
	field = "\t\tprivate static final Field FIELD_#(var.name);\n",
	fieldget = "\t\t\t\tFIELD_#(var.name) = _c_.getDeclaredField(\"#(var.name)\"); FIELD_#(var.name).setAccessible(true);\n",
	new = "\t\t#(var.name) = null;\n",
	init = "this.#(var.name) = #(var.name)",
	reset = "#(var.name) = null",
	assign = "this.#(var.name) = _b_.#(var.name)",
	set = [[

	/** @param #(var.name) #(var.comment1) */
	public void set#(var.name_u)(#(var.type) #(var.name))
	{
		this.#(var.name) = #(var.name);
	}
]],
	getsafe = [[

		/** @return #(var.comment1) */
		public #(var.type) get#(var.name_u)()
		{
			return _bean.get#(var.name_u)();
		}
]],
	setsafe = function() return [[

		/** @param #(var.name) #(var.comment1) */
		public void set#(var.name_u)(#(var.type) #(var.name))
		{
			if (initSContext()) _sctx.addOnRollback(new SBase.SObject(_bean, FIELD_#(var.name), _bean.get#(var.name_u)()));
			_bean.set#(var.name_u)(#(var.name));
		}
]] end,
	marshal = "",
	unmarshal = "",
	unmarshal_kv = "",
	hashcode = "0",
	equals = "",
	compareto = "0",
	tostring = "_s_.append(this.#(var.name))",
})
typedef.boolean = typedef.bool
typedef.integer = typedef.int
typedef.binary = typedef.octets
typedef.bytes = typedef.octets
typedef.data = typedef.octets
typedef.array = typedef.vector
typedef.arraydeque = typedef.deque
typedef.arraylist = typedef.vector
typedef.linkedlist = typedef.list
typedef.set = typedef.hashset
typedef.linkedset = typedef.linkedhashset
typedef.map = typedef.hashmap
typedef.linkedmap = typedef.linkedhashmap

local function trim(s)
	return s:gsub("[%c%s]+", "")
end
local function do_var(var)
	if type(var.id) ~= "number" then var.id = -1 end
	if var.id < -1 or var.id > 190 then error("ERROR: var.id=" .. var.id .. " must be in [1, 190]") end
	var.import = {}
	var.id3 = format("%3d", var.id)
	var.name = trim(var.name)
	var.type = trim(var.type)
	if var.comment and #var.comment > 0 then
		var.comment1 = var.comment:gsub("%c", " ")
		var.comment2 = " // " .. var.comment1
	else
		var.comment1 = ""
		var.comment2 = ""
	end
	if type(var.value) == "string" then var.value = "\"" .. var.value .. "\"" end
	var.value = var.value and " = " .. var.value or ""
	local basetype
	basetype, var.k, var.v, var.cap = var.type:match "^%s*([%w_%.]+)%s*<?%s*([%w_%.]*)%s*,?%s*([%w_%.]*)%s*>?%s*%(?%s*([%w_%.]*)%s*%)?%s*$"
	if not var.cap then var.cap = "" end
	local def = typedef[basetype]
	if not def and typedef[lower(basetype)] then basetype = lower(basetype) def = typedef[basetype] end
	if var.k and not typedef[var.k] and typedef[lower(var.k)] then var.k = lower(var.k) end
	if var.v and not typedef[var.v] and typedef[lower(var.v)] then var.v = lower(var.v) end
	if not def then def = var.id > 0 and typedef.bean or typedef.ref end
	if type(def) == "table" then
		for k, v in pairs(def) do
			if type(v) == "function" then v = v(var) end
			if k == "import" then
				for _, s in ipairs(v) do var.import[#var.import + 1] = s end
			else
				var[k] = v
			end
		end
	else
		error("ERROR: unknown type: " .. var.type .. " => " .. basetype)
	end
end
local function code_conv(code, prefix, t)
	return code:gsub("#%(" .. prefix .. "%.([%w_]+)%)", function(name) return t[name] end)
end
local uid_used = {}
local function gen_uid(beanname, s)
	local h = 0
	for i = 1, #s do
		h = h % 0x10000000000 * 4093 + 1 + s:byte(i)
	end
	h = format("0xbeac%04x%08xL", math.floor(h / 0x100000000) % 0x10000, h % 0x100000000)
	if uid_used[h] then
		error("ERROR: hash collisions in gen_uid(" .. uid_used[h] .. ", " .. beanname .. "): " ..  h)
	end
	uid_used[h] = beanname
	return h
end

local name_code = {} -- bean name => bean code
local name_bean = {} -- bean name => bean
local handlers = {} -- selected handler name => handler path
local has_handler -- any selected handler?
local all_handlers = {} -- all handlers name => true
local hdl_names = {} -- handler name => {bean names}
local tables = { imports = { ["java.util.HashMap"] = true, ["jane.core.Bean"] = true } }
function handler(hdls)
	if not arg[2] then error("ERROR: arg[2] must be handler name(s)") end
	for hdlname in arg[2]:gmatch("([%w_%.]+)") do
		local hdl = hdls[hdlname]
		if not hdl then error("ERROR: not found or unknown handler name: " .. hdlname) end
		for hdlname, hdlpath in pairs(hdl) do
			if type(handlers[hdlname]) ~= "string" then
				handlers[hdlname] = hdlpath
				if hdlname ~= "dbt" and hdlpath ~= true then
					has_handler = true
				end
			end
		end
	end
	for _, v in pairs(hdls) do
		for k in pairs(v) do
			all_handlers[k] = true
		end
	end
end
local function bean_common(bean)
	bean.name = trim(bean.name)
	if bean.name:find("[^%w_]") or typedef[bean.name] or bean.name == "AllBeans" or bean.name == "AllTables" then error("ERROR: invalid bean.name: " .. bean.name) end
	if name_code[lower(bean.name)] then error("ERROR: duplicated bean.name: " .. bean.name) end
	if type(bean.type) ~= "number" or not bean.handlers then bean.type = 0 end
	if not bean.initsize then bean.initsize = 0 end
	bean.final = (bean.extend and "" or "final ")
	bean.modifier = (bean.extend and "protected" or "private")
	for name in (bean.handlers or ""):gmatch("([%w_%.]+)") do
		if not all_handlers[name] then error("ERROR: not defined handler: " .. name) end
		hdl_names[name] = hdl_names[name] or {}
		hdl_names[name][#hdl_names[name] + 1] = bean.name
	end
	name_bean[bean.name] = bean
	bean.comment = bean.comment and #bean.comment > 0 and "\n/**\n * " .. bean.comment:gsub("\n", "<br>\n * ") .. "\n */" or ""
end
local function bean_const(code)
	return code:gsub("public  /%*", "private /*"):
		gsub("\t@Override\n\tpublic void assign%(.-\n\t}\n\n", ""):
		gsub("\t/%*%* [^\n]*\n\tpublic void set.-\n\t}\n\n", ""):
		gsub("\n\tpublic static final class Safe.-}\n\t}\n", ""):
		gsub("import java%.lang%.reflect%.Field;\n", ""):
		gsub("import jane%.core%.DynBean;\n", ""):
		gsub("import jane%.core%.SBase;\n", ""):
		gsub("import jane%.core%.SContext;\n", ""):
--		gsub("\tprivate static final Field .-\n", ""):
--		gsub("\tstatic\n.-\n\t}\n\n", ""):
--		gsub("\n\tpublic [%w_]+%(%).-\n\t}\n", ""):
--		gsub("(\n\tprivate /%*[ %d]+%*/)", "%1 final"):
--		gsub("\n\tpublic  static final [%w_]+ BEAN_STUB = new [%w_]+%(%);\n", ""):
--		gsub("return BEAN_STUB", "throw new UnsupportedOperationException()"):
--		gsub("return new [%w_]+%(%)", "throw new UnsupportedOperationException()"):
		gsub("\tpublic void marshal.-\n\t}\n\n", ""):
		gsub("\tpublic <B extends Bean<B>> B unmarshal.-\n\t}\n\n", ""):
		gsub("\tpublic DynBean unmarshal.-\n\t}\n\n", ""):
		gsub("\n\t@Override\n\tpublic Safe safe.-\n\t}\n", ""):
		gsub("\t@Override\n\tpublic void reset%(.-\n\t}", [[
	@Deprecated
	@Override
	public void reset()
	{
		throw new UnsupportedOperationException();
	}]]):
		gsub("\t@Override\n\tpublic OctetsStream unmarshal%(", "\t@Deprecated\n\t@Override\n\tpublic OctetsStream unmarshal%(")
end
local function get_imports(import)
	local imports = {}
	for k in pairs(import) do
		imports[#imports + 1] = k
	end
	sort(imports, function(a, b) return a:gsub("^java", ".") < b:gsub("^java", ".") end)
	return concat(imports, ";\nimport ")
end
function bean(bean)
	bean_common(bean)

	bean.import = { ["jane.core.Bean"] = true, ["jane.core.MarshalException"] = true, ["jane.core.Octets"] = true, ["jane.core.OctetsStream"] = true, ["jane.core.SContext"] = true }
	local vartypes = { bean.name }
	local id_used = {}
	for _, var in ipairs(bean) do
		do_var(var)
		if var.id > 0 then
			if id_used[var.id] then error("ERROR: duplicated var.id: " .. var.id .. " in bean: " .. bean.name) end
			id_used[var.id] = true
			vartypes[#vartypes + 1] = var.type
		end
		for _, v in ipairs(var.import) do
			bean.import[v] = true
		end
	end

	if not bean.maxsize then bean.maxsize = 0x7fffffff end
	if type(bean.initsize) == "number" and bean.initsize > 0x10000 then print("WARNING: bean.initsize = " .. bean.initsize .. " > 64KB (bean.name:" .. bean.name .. ")") end
	bean.imports = get_imports(bean.import)
	bean.uid = gen_uid(bean.name, concat(vartypes))
	if not bean.attach_java then bean.attach_java = "" end

	local code = template_bean:gsub("#{#(.-)#}#", function(body)
		local subcode = {}
		for _, var in ipairs(bean) do
			if var.id == -1 then subcode[#subcode + 1] = code_conv(body, "var", var) end
		end
		return concat(subcode)
	end):gsub("#%(#(.-)#%)#", function(body)
		local subcode = {}
		for _, var in ipairs(bean) do
			if var.id >= 0 then subcode[#subcode + 1] = code_conv(code_conv(body, "var", var), "var", var) end
		end
		local code = concat(subcode)
		return code:sub(-2, -1) ~= ", " and code or code:sub(1, -3)
	end)

	bean.param_warning = (#vartypes > 1 and "" or "/** @param _b_ unused */\n\t")
	code = code_conv(code, "bean", bean):
		gsub("\r", ""):
		gsub(#vartypes > 1 and "#[<>]#" or "#<#.-#>#", ""):
		gsub("\n\t\tstatic\n\t\t{\n\t\t\ttry\n\t\t\t{\n\t\t\t\tClass<.-> _c_ = .-%.class;\n\t\t\t}\n\t\t\tcatch %(Exception e%)\n\t\t\t{\n\t\t\t\tthrow new Error%(e%);\n\t\t\t}\n\t\t}\n\n", ""):
		gsub("int h = (%(int%)serialVersionUID;)\n\t\treturn h;", "return %1"):
		gsub("\t+/%*%* @return  %*/\n", ""):
		gsub("\t+/%*%* @param [%w_]+  %*/\n", ""):
		gsub("\t+/%*%*  %*/\n", ""):
		gsub("\n\t{\n\n\t\t", "\n\t{\n\t\t"):
		gsub("\t\t_h_ = _h_ %* 16777619 %+ 0;\n", ""):
		gsub("\t\tif %(%) return false;\n", ""):
		gsub("\t\t_c_ = 0; if %(_c_ != 0%) return _c_;\n", ""):
		gsub("( new %w+)<.->", "%1<>"):
		gsub("%.append%(','%);\n\t\treturn _s_", ";\n\t\treturn _s_"):
		gsub("\n\n\n", "\n\n")
	if not code:find("\tprivate static final Field ") then
		code = code:gsub("import java%.lang%.reflect%.Field;\n", "")
	end
	if bean.const then code = bean_const(code) end
	local c, n = code:gsub([[
	static
	{
		try
		{
			Class<[%w_%.]+> _c_ = [%w_%.]+%.class;
		}
		catch %(Exception e%)
		{
		}
	}

]], "")
	if n > 0 then code = c:gsub("import java%.lang%.reflect%.Field;\n", "") end
	name_code[bean.name] = code
	name_code[lower(bean.name)] = code
end

local outpath = (arg[4] or "."):gsub("\\", "/")
if outpath:sub(-1, -1) ~= "/" then outpath = outpath .. "/" end
local function checksave(fn, d, change_count)
	local f = open(fn, "rb")
	if f then
		local s = f:read "*a"
		f:close()
		if change_count > 0 then
			d = s:gsub("\n\t/%*\\.-\n\t\\%*/", d:match("\n\t/%*\\.-\n\t\\%*/"), change_count)
		end
		if s == d then d = nil else print(" * " .. fn) end
	else
		print("+  " .. fn)
	end
	if d then
		f = open(fn, "wb")
		if not f then error("ERROR: can not create file: " .. fn) end
		f:write(d)
		f:close()
	end
end

local need_save = {}
local function savebean(beanname, safe)
	local s = need_save[beanname]
	if s ~= nil then
		if safe and not s then need_save[beanname] = true end
		return
	end
	need_save[beanname] = safe or false
	local bean = name_bean[beanname]
	if not bean then
		error("ERROR: not found bean.name: " .. beanname)
	end
	for _, var in ipairs(bean) do
		if name_bean[var.type] then savebean(var.type, safe) end
		if name_bean[var.k] then savebean(var.k, safe) end
		if name_bean[var.v] then savebean(var.v, safe) end
	end
end

local key_conv = { int = "Integer", integer = "Integer", Integer = "Integer", long = "Long", Long = "Long", float = "Float", Float = "Float", double = "Double", Double = "Double",
					string = "String", String = "String", binary = "Octets", bytes = "Octets", data = "Octets", octets = "Octets", Octets = "Octets" }
local need_save_dbt = {}
function dbt(table)
	if not handlers.dbt or handlers.dbt ~= true and table.handler ~= handlers.dbt then return end
	if not table.id then table.id = -1 end
	if not table.cachesize then table.cachesize = 0 end
	local key_type = key_conv[table.key]
	if key_type then
		if key_type == "Octets" then tables.imports["jane.core.Octets"] = true end
		table.table = "Table"
		table.key = key_type
			if key_type == "String" then table.keys = "\"\""; table.keyg = "String.class"
		elseif key_type == "Octets" then table.keys = "new Octets()"; table.keyg = "Octets.class"
		else table.keys = "new " .. key_type .. "(0)"; table.keyg = key_type .. ".class" end
		table.comma = ", "
		tables.imports["jane.core.Table"] = true
	elseif table.key == "id" then
		table.table = "TableLong"
		table.key = ""
		table.keys = ""
		table.keyg = "long.class"
		table.comma = ""
		tables.imports["jane.core.TableLong"] = true
	else
		table.table = "Table"
		table.keys = "#(table.key).BEAN_STUB"
		table.keyg = table.keys
		table.comma = ", "
		tables.imports["jane.core.Table"] = true
		need_save_dbt[table.key] = true
	end
	table.values = "#(table.value).BEAN_STUB"
	table.lock = table.lock or ""
	if table.comment and #table.comment > 0 then table.comment = "/**\n\t * " .. table.comment:gsub("\n", "<br>\n\t * ") .. "\n\t */\n\t" end
	tables[#tables + 1] = table
	need_save_dbt[table.value] = true
end

if not arg[3] then error("ERROR: arg[3] must be input allbeans.lua") end
dofile(arg[3])

for beanname in pairs(need_save_dbt) do
	savebean(beanname, true)
end

local namespace_path = namespace:gsub("%.", "/")
checksave(outpath .. namespace_path .. "/AllBeans.java", (template_allbeans:gsub("#%[#(.-)#%]#", function(body)
	local subcode = {}
	for hdlname, hdlpath in spairs(handlers) do
		if hdlname ~= "dbt" then
			local names = hdl_names[hdlname] or {}
			local hdl = { name = hdlname, path = tostring(hdlpath), count = 0 }
			subcode[#subcode + 1] = code_conv(body:gsub("#%(#(.-)#%)#", function(body)
				local subcode2 = {}
				local saved = {}
				local typed = {}
				for _, name in ipairs(names) do
					if not saved[name] then
						saved[name] = true
						local bean = name_bean[name]
						savebean(bean.name)
						if bean.type ~= 0 then
							if typed[bean.type] then error("ERROR: duplicated bean.type: " .. bean.type .. " (" .. typed[bean.type] .. ", " .. bean.name .. ") for " .. hdlname) end
							typed[bean.type] = name
							hdl.count = hdl.count + 1
							subcode2[#subcode2 + 1] = code_conv(body, "bean", bean)
							if type(hdlpath) == "string" then
								if bean.type < 0 or bean.type > 0x7fffffff then error("ERROR: invalid bean.type: " .. tostring(bean.type) .. " (bean.name: " .. bean.name .. ")") end
								checksave(outpath .. hdlpath:gsub("%.", "/") .. "/" .. bean.name .. "Handler.java", code_conv(code_conv(template_bean_handler:gsub("#%(#(.-)#%)#", function(body)
									local subcode3 = {}
									for _, var in ipairs(bean) do
										subcode3[#subcode3 + 1] = code_conv(body, "var", var)
									end
									return concat(subcode3)
								end), "hdl", hdl), "bean", bean):gsub("\r", ""), 1)
							end
						end
					end
				end
				return concat(subcode2)
			end), "hdl", hdl)
			if type(hdlpath) ~= "string" then subcode[#subcode] = "" end
		end
	end
	return concat(subcode)
end):gsub(has_handler and "#[<>]#" or "#<#.-#>#", ""):gsub("\r", "")), 0)

tables.count = #tables
tables.imports["java.util.ArrayList"] = true
tables.imports["java.util.function.Consumer"] = true
tables.imports["jane.core.DBManager"] = true
tables.imports["jane.core.TableBase"] = true
tables.imports["jane.core.map.IntHashMap"] = true
tables.imports = get_imports(tables.imports)
if tables.count > 0 then
	checksave(outpath .. namespace_path .. "/AllTables.java", (code_conv(template_alltables:gsub("#%(#(.-)#%)#", function(body)
		local subcode = {}
		local names = {}
		local ids = {}
		for _, table in ipairs(tables) do
			if names[table.name] then error("ERROR: duplicated table.name: " .. table.name) end
			if ids[table.id] and table.id >= 0 then error("ERROR: duplicated table.id: " .. table.id) end
			if table.id < -0x80000000 or table.id > 0x7fffffff then error("ERROR: invalid table.id: " .. table.id) end
			names[table.name] = true
			ids[table.id] = true
			subcode[#subcode + 1] = code_conv(code_conv(body, "table", table), "table", table)
		end
		return concat(subcode)
	end), "tables", tables):gsub(#tables > 0 and "#[<>]#" or "#<#.-#>#", ""):gsub("\r", "")), 0)
end

for beanname, safe in spairs(need_save) do
	local code = name_code[beanname]
	if not code then error("ERROR: unknown bean: " .. beanname) end
	if not safe then
		code = code:gsub("\n\t@Override\n\tpublic Safe safe%(.*", "}\n")
				   :gsub("import java%.lang%.reflect%.Field;\n", "")
				   :gsub("import jane%.core%.S.-\n", "")
	end
	checksave(outpath .. namespace_path .. "/" .. beanname .. ".java", code:gsub("\r", ""), 0)
end

print((arg[2] or "") .. " ... done!")
